/////////////////////////////////////////////////////////////////////////////
//
// (c) 2007 BinaryComponents Ltd.  All Rights Reserved.
//
// http://www.binarycomponents.com/
//
/////////////////////////////////////////////////////////////////////////////

using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using SuperListEx.Helper;
using SuperListEx.Sections;

namespace SuperListEx.Sections
{
  public class HeaderColumnSection : Section
  {
    #region vars
    private int _reservedColumnSpace = 0;
    private bool _leftMouseButtonPressed = false;
    private int _oldWidth;
    private const int _arrowSpaceXMargin = 2;
    private const int _arrowWidth = 10;
    private DisplayMode _displayMode;
    private Column _column;
    private const int _hotSpotWidth = 20;
    #endregion

    #region properties
    public override bool CanDropInVoid
    {
      get
      {
        return Parent.AllowColumnsToBeDroppedInVoid;
      }
    }

    public Column Column
    {
      get
      {
        return _column;
      }
    }

    public new HeaderColumnSectionContainer Parent
    {
      get
      {
        return (HeaderColumnSectionContainer)base.Parent;
      }
    }


    public bool LeftMouseButtonPressed
    {
      get
      {
        return _leftMouseButtonPressed;
      }
    }

    public DisplayMode Mode
    {
      get
      {
        return _displayMode;
      }
    }

    public int ArrowWidth
    {
      get
      {
        return _arrowWidth;
      }
    }

    public int ArrowSpaceXMargin
    {
      get
      {
        return _arrowSpaceXMargin;
      }
    }

    private ListSection ListSection
    {
      get
      {
        for (Section parent = Parent; parent != null; parent = parent.Parent)
        {
          ListSection listSection = parent as ListSection;
          if (listSection != null)
          {
            return listSection;
          }
        }
        return null;
      }
    }
    #endregion

    #region constructors
    public HeaderColumnSection(ISectionHost host, DisplayMode displayMode, Column column)
      : base(host)
    {
      Debug.Assert(column != null);
      CanDrag = true;
      _displayMode = displayMode;
      _column = column;
      column.DataChanged += column_DataChanged;
    }
    #endregion

    #region Paint Functions
    public override void Paint(GraphicsSettings gs, Rectangle clipRect)
    {
      using (SolidBrush brush = new SolidBrush(Host.TextColor))
      {
        Rectangle rc = Rectangle;
        int offset = 0;

        if (Parent.LayoutController != null)
        {
          offset = Parent.LayoutController.CurrentHorizontalScrollPosition;
          rc.X -= offset;
        }

        if (_displayMode == DisplayMode.Header)
        {
          if (VisualStyleRenderer.IsSupported)
          {
            VisualStyleRenderer renderer = GetRenderer();

            renderer.DrawBackground(gs.Graphics, rc);
          }
          else
          {
            gs.Graphics.FillRectangle(SystemBrushes.Control, rc);

            if (_leftMouseButtonPressed)
            {
              ControlPaint.DrawBorder3D(gs.Graphics, rc, Border3DStyle.Sunken);
            }
            else
            {
              ControlPaint.DrawBorder3D(gs.Graphics, rc, Border3DStyle.Raised);
            }
          }
        }
        else
        {
          DrawBox(gs.Graphics, rc);
        }

        DrawIcon(gs.Graphics, ref rc);

        const int textMargin = 2;

        rc.X += textMargin;
        rc.Width -= textMargin;

        if (Parent.LayoutController != null)
        {
          if (_column.ShowHeaderSortArrow)
          {
            rc.Width -= _arrowWidth + _arrowSpaceXMargin * 2;
          }
        }

        rc.Y -= 2;

        DrawCaption(gs, rc);

        DrawSortArrow(gs, rc);
      }
    }

    protected virtual void DrawCaption(GraphicsSettings gs, Rectangle rc)
    {
      TextRendererEx.DrawText(gs.Graphics, _column.Name, SystemFonts.MenuFont, rc, Color.Black, GetTextFormatFlags());
    }

    protected virtual void DrawBox(Graphics g, Rectangle rc)
    {
      ControlPaint.DrawBorder3D(g, rc.Left, rc.Top, rc.Width, rc.Height - 1, Border3DStyle.RaisedInner, Border3DSide.All);
    }

    protected virtual void DrawSortArrow(GraphicsSettings gs, Rectangle rc)
    {
      if (!_column.ShowHeaderSortArrow)
      {
        return;
      }

      if (rc.Width <= 0)
      {
        return;
      }

      int offset = 0;

      if (Parent.LayoutController != null)
      {
        offset = Parent.LayoutController.CurrentHorizontalScrollPosition;
      }
      //
      // Draw sort arrows
      const int halfArrowWidth = _arrowWidth / 2;
      int right = Rectangle.Right - offset;
      Rectangle rcArrow = new Rectangle(right - _arrowWidth - _arrowSpaceXMargin * 2,
                                          Rectangle.Y + (Rectangle.Height - _arrowWidth) / 2,
                                          _arrowWidth,
                                          _arrowWidth);

      SortOrder sortOrder = Parent.GetColumnSortOrder(Column);

      switch (sortOrder)
      {
        case SortOrder.Ascending:
          {
            int xTop = rcArrow.Left + rcArrow.Width / 2 + 1;
            int yTop = rcArrow.Top + (rcArrow.Height - halfArrowWidth) / 2 - 1;
            int xLeft = xTop - halfArrowWidth;
            int yLeft = yTop + halfArrowWidth + 1;
            int xRight = xTop + halfArrowWidth + 1;
            int yRight = yTop + halfArrowWidth + 1;

            gs.Graphics.FillPolygon(SystemBrushes.ControlDark, new Point[]
                                                                               {
                                                                                   new Point(xTop, yTop),
                                                                                   new Point(xLeft, yLeft),
                                                                                   new Point(xRight, yRight)
                                                                               });
          }
          break;
        case SortOrder.Descending:
          {
            int xBottom = rcArrow.Left + rcArrow.Width / 2 + 1;
            int xLeft = xBottom - halfArrowWidth + 1;
            int yLeft = rcArrow.Top + (rcArrow.Height - halfArrowWidth) / 2;
            int xRight = xBottom + halfArrowWidth;
            int yRight = rcArrow.Top + (rcArrow.Height - halfArrowWidth) / 2;
            int yBottom = yRight + halfArrowWidth;

            gs.Graphics.FillPolygon(SystemBrushes.ControlDark, new Point[]
                                                                               {
                                                                                   new Point(xLeft, yLeft),
                                                                                   new Point(xBottom, yBottom),
                                                                                   new Point(xRight, yRight)
                                                                               });
          }
          break;
      }
    }

    private void DrawIcon(Graphics g, ref Rectangle rc)
    {
      if (_column.HeaderIcon == null)
      {
        return;
      }
      if (rc.Width < _column.HeaderIcon.Size.Width)
      {
        return;
      }
      if (rc.Height < _column.HeaderIcon.Size.Height)
      {
        return;
      }
      g.DrawIcon(_column.HeaderIcon, rc.Left, rc.Top);
      rc.Offset(_column.HeaderIcon.Size.Width, 0);
      rc.Width -= _column.HeaderIcon.Size.Width;
    }
    #endregion

    #region public functions
    public override void Dispose()
    {
      base.Dispose();
      if (_column != null)
      {
        _column.DataChanged -= column_DataChanged;
        _column = null;
      }
    }

    public override void DroppedInVoid()
    {
      ListControl listControl = Host as ListControl;
      if (listControl != null && !listControl.ShowCustomizeSection)
      {
        return;
      }

      Parent.Columns.Remove(Column);
    }

    protected virtual TextFormatFlags GetTextFormatFlags()
    {
      return TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.EndEllipsis;
    }

    public override void Layout(GraphicsSettings gs, Size maximumSize)
    {
      const int widthPadding = 10;
      int headerWidth;
      int height;

      if (VisualStyleRenderer.IsSupported)
      {
        VisualStyleRenderer renderer = GetRenderer();

        height = renderer.GetPartSize(gs.Graphics, ThemeSizeType.True).Height;
      }
      else
      {
        height = SystemFonts.DialogFont.Height + 6;
      }
      if (height > maximumSize.Height)
      {
        height = maximumSize.Height;
      }

      switch (_displayMode)
      {
        case DisplayMode.Header:
          headerWidth = _column.Width;
          break;
        case DisplayMode.Customise:
          headerWidth = TextRenderer.MeasureText(_column.Caption, SystemFonts.MenuFont).Width + widthPadding + _arrowWidth + _arrowSpaceXMargin * 2;
          break;
        default:
          throw new NotSupportedException();
      }
      if (height < MinimumHeight)
      {
        height = MinimumHeight;
      }
      Size = new Size(headerWidth, height);
    }

    public override void KeyDown(KeyEventArgs e)
    {
      base.KeyDown(e);

      if (e.KeyCode == Keys.Escape && Host.SectionWithMouseCapture == this)
      {
        CancelMouseCapture();
      }
    }

    public override Section SectionFromPoint(Point pt)
    {
      Rectangle rc = Rectangle;

      if (_displayMode == DisplayMode.Header)
      {
        rc.Width += _hotSpotWidth / 2;
      }
      if (rc.Contains(pt))
      {
        return this;
      }
      return null;
    }
    #endregion

    #region mouse
    public override void MouseMove(Point pt, MouseEventArgs e)
    {
      if (_displayMode == DisplayMode.Header && (Host.SectionWithMouseCapture == this || PointInChangeWidthHotSpot(pt)))
      {
        #region Fixed Column Header Width

        if (Column.IsFixedWidth)
        {
          Host.Cursor = Cursors.Default;
          base.MouseMove(pt, e);
          return;
        }

        #endregion

        Host.Cursor = Cursors.VSplit;
        if (Host.SectionWithMouseCapture == this)
        {
          int newWidth = pt.X - HostBasedRectangle.Left - _reservedColumnSpace;

          if (newWidth < _hotSpotWidth / 2)
          {
            newWidth = Math.Max(_hotSpotWidth / 2, _reservedColumnSpace);
          }
          Column.Width = newWidth;
        }
        else if (_leftMouseButtonPressed && e.Button == MouseButtons.Left)
        {
          _oldWidth = Column.Width;
          _reservedColumnSpace = Rectangle.Width - Column.Width;
          Host.StartMouseCapture(this);
        }
      }
      else
      {
        Host.Cursor = Cursors.Default;
        base.MouseMove(pt, e);
      }
    }

    public override bool MouseDoubleClick(Point pt)
    {
      if (_displayMode == DisplayMode.Header && PointInChangeWidthHotSpot(pt))
      {
        ListSection listSection = ListSection;
        if (listSection != null)
        {
          listSection.SizeColumnsToFit(Column);
        }
      }
      return true;
    }

    public override void MouseClick(MouseEventArgs e)
    {
      base.MouseClick(e);

      ListControl listControl = Host as ListControl;
      if (listControl != null && !listControl.AllowSorting)
      {
        return;
      }

      Point pt = new Point(e.X, e.Y);
      if (!PointInChangeWidthHotSpot(pt))
      {
        switch (Parent.GetColumnSortOrder(Column))
        {
          case SortOrder.Ascending:
            Parent.SetColumnSortOrder(Column, SortOrder.Descending);
            break;

          case SortOrder.None:
          case SortOrder.Descending:
            Parent.SetColumnSortOrder(Column, SortOrder.Ascending);
            break;
        }
      }
    }

    public override void MouseUp(MouseEventArgs e)
    {
      base.MouseUp(e);

      if (!Host.IsInDragOperation)
      {
        if (Host.SectionWithMouseCapture == this)
        {
          Host.EndMouseCapture();
        }
      }
      _leftMouseButtonPressed = false;
      Invalidate();
    }

    public override void CancelMouseCapture()
    {
      _leftMouseButtonPressed = false;
      Host.Cursor = Cursors.Default;
      base.CancelMouseCapture();
      Column.Width = _oldWidth;
      Host.EndMouseCapture();
    }

    public override void MouseEnter()
    {
      base.MouseEnter();
      Invalidate();
    }

    public override void MouseLeave()
    {
      Host.Cursor = Cursors.Default;
      base.MouseLeave();
      Invalidate();
    }

    public override void MouseDown(MouseEventArgs e)
    {
      base.MouseDown(e);

      if (e.Button == MouseButtons.Left)
      {
        _leftMouseButtonPressed = true;
      }
      Invalidate();
    }
    #endregion

    #region private funtions
    private bool PointInChangeWidthHotSpot(Point pt)
    {
      Rectangle rc = HostBasedRectangle;

      rc.X = rc.Right - _hotSpotWidth / 2;
      rc.Width = _hotSpotWidth;

      return rc.Contains(pt);
    }

    private VisualStyleRenderer GetRenderer()
    {
      VisualStyleElement item;

      if (_leftMouseButtonPressed)
      {
        item = VisualStyleElement.Header.Item.Pressed;
      }
      else
      {
        if (Host.SectionMouseOver == this)
        {
          item = VisualStyleElement.Header.Item.Hot;
        }
        else
        {
          item = VisualStyleElement.Header.Item.Normal;
        }
      }

      VisualStyleRenderer renderer = new VisualStyleRenderer(item);

      return renderer;
    }

    private void column_DataChanged(object sender, ColumnDataChangedEventArgs eventArgs)
    {
      if (eventArgs.WhatChanged == WhatPropertyChanged.Width)
      {
        Host.LazyLayout(null);
      }
    }
    #endregion
  }
}